const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.mkfs);

const Disk = @import("mkfs/Disk.zig");
const Inode = @import("mkfs/Inode.zig");

pub const native_endian = @import("builtin").cpu.arch.endian();
pub const target_arch = @field(std.Target.Cpu.Arch, @import("target").arch);

const fs = @import("fs");
const nlog = fs.LOGBLOCKS + 1; // Header followed by LOGBLOCKS data blocks.

/// size of file system in blocks
const FSSIZE = 32000;
const NINODES = 200;
const ninodeblocks = NINODES / fs.IPB + 1;

pub const inodestart = 2 + nlog;
pub const bmapstart = inodestart + ninodeblocks;

pub fn main() !void {
    var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
    defer arena_allocator.deinit();
    const allocator = arena_allocator.allocator();

    const args = try std.process.argsAlloc(allocator);
    defer allocator.free(args);

    if (args.len < 3) {
        log.err("Usage: {s} <fs.img> </path/to/rootfs> [nblocks]", .{args[0]});
        return;
    }

    const fs_name = args[1];
    const rootfs = args[2];
    const fs_size = if (args.len >= 4) try std.fmt.parseInt(u32, args[3], 0) else FSSIZE;

    const nbitmap = fs_size / fs.BPB + 1;
    const nmeta = bmapstart + nbitmap;
    const nblocks = fs_size - nmeta;

    log.info(
        "nmeta {} (boot, super, log blocks {}, inode blocks {}, bitmap blocks {}) blocks {} total {}",
        .{ nmeta, nlog, ninodeblocks, nbitmap, nblocks, fs_size },
    );

    // initialize disk with the output file path
    var disk: Disk = try .init(allocator, fs_name, fs_size, nbitmap);
    defer disk.deinit();

    // write super block
    var sblock: fs.SuperBlock = .{
        .size = fs_size,
        .nblocks = nblocks,
        .ninodes = NINODES,
        .nlog = nlog,
        .logstart = 2,
        .inodestart = inodestart,
        .bmapstart = bmapstart,
    };
    if (native_endian != target_arch.endian()) {
        std.mem.byteSwapAllFields(fs.SuperBlock, &sblock);
    }
    _ = try disk.write(1, 0, std.mem.asBytes(&sblock));

    // allocate root inode
    var root: Inode = .alloc(&disk, .dir);
    defer root.commit();
    assert(root.number == fs.ROOTINO);

    // recursively copies all files and directories from host to filesystem
    var rootfs_dir = try std.fs.cwd().openDir(rootfs, .{ .iterate = true });
    defer rootfs_dir.close();
    try disk.walk(rootfs_dir, &root, &root);
}
